﻿using System;
using System.Windows;
using System.Collections.Generic;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Media;
using Bling.DSL;
using Bling.WPF;
using Bling.Shapes;
using Bling.Animation;
using Bling.Util;
using Bling.Physics;

namespace Bling.PhysicsTutorial {

  /// <summary>
  /// Similar to the Bezier example in the main tutorial, except points on the curves are controlled via physics!
  /// </summary>
  public class BezierTest : PhysicsCanvas {
    public BezierTest() : base("Bezier", 10) { }
    protected override Action Init(CanvasBl MainCanvas, World World) {
      PointBl r = new Random();

      // Need 8 thumbs to form the four quadratic curves of the image. .
      PlainBlock<Thumb, ThumbBl> blockA = new PlainBlock<Thumb, ThumbBl>(World, 8, i => {
        return new ThumbBl(MainCanvas) {
          CenterPosition = { Now = r * new PointBl(500, 500) },
          CanDrag = true, Background = i.SelectColor(), ZIndex = 3,
        };
      });

      PointBl[] Points = new PointBl[blockA.Count];

      // Create a new shape from four bezier curves.
      PathShapeBl shape = new PathShapeBl(MainCanvas) {
        Start = { Now = new PointBl(50, 50) },
        Fill = Brushes.White,
      };
      {
        var i = 0;
        Points[0] = shape.Start;
        for (int j = 0; j < 4; j++) {
          var thumbA = blockA[(i + 1)].At;
          var thumbB = blockA[(i + 2) % blockA.Count].At;
          // loop around.
          QuadraticBezierSegmentBl segment = new QuadraticBezierSegmentBl() {
            Point2 = blockA[(i + 2) % blockA.Count].At,
          };
          // assign the array.
          Points[(i + 1)] = segment.Point1;
          if (i + 2 == Points.Length) shape.Start = thumbB;
          else Points[(i + 2)] = segment.Point2;

          PointBl.Bezier(.5, 1, Points[i], segment.Point1, segment.Point2).Bind = thumbA;
          shape.Segments.Add(segment);
          i += 2;
        }
        (i == Points.Length).Assert();
      }
      // physical constraints
      {
        // Again, a physical position for each thumb (we did this in Chain also).
        var PositionProperty = blockA.NewBodyProperty<Point>("Position");
        Func<PlainBlock<Thumb, ThumbBl>.Body, PointBl> Position = (body) => body.GetProperty(PositionProperty);
        blockA.ForAll(body => {
          // Initialize, link, and declare step for physical position (same as in Chain).
          Position(body).Init = body.At.CenterPosition;
          Position(body).Link[!body.At.IsDragging] = body.At.CenterPosition;
          Position(body).Step = (now, old) => now.VerletStep(0.05, old);
          { // three constraints, so only 33% strength for each.
            // These two constraints keep the points 200 pixels away from adjacent points. 
            Position(body).Relax[.33d] = Position(body).Spring(Position(body.Prev[1]), 200d);
            Position(body).Relax[.33d] = Position(body).Spring(Position(body.Next[1]), 200d);
            var across = body.Next[blockA.Count / 2];
            var acrossLength = ((body.Index % 2) == 0).Condition(Math.Sqrt(400d * 400d * 2d), 400d);
            // This is a lateral spring that keeps the rectangle shape of the image.
            Position(body).Relax[.33d] = Position(body).Spring(Position(across), acrossLength);
          }
          DoubleBl idx = (body.Index + 3) % blockA.Count;
          // Doing some math!
          var leftTop = new PointBl(100, 100);
          var dx = (200d.Bl() * body.Index).Min(400d);
          dx = (body.Index <= 4).Condition(dx, (400d.Bl() - (body.Index - 4) * 200d.Bl()).Max(0d));

          var dy = 0d + ((body.Index - 2) * 200d.Bl()).Clamp(0, 400d);
          dy = (body.Index <= 6).Condition(dy, 400d.Bl() - (body.Index - 6) * 200d.Bl());
          // a weak constraint that causes the shape to float back to a fixed position/orientation.
          Position(body).Relax[0.01] = leftTop + new PointBl(dx, dy);

          // restitution and clamping. 
          Position(body).Old = Position(body).Old.Restitution(Position(body), new Point(0, 0), MainCanvas.Size);
          Position(body).Bind = Position(body).Clamp(new Point(0, 0), MainCanvas.Size);
        });
      }
      { // pixel shader to impelment a genie effect, same as the Bezier example in the main tutorial.
        DoubleBl minX = Points[0].X;
        DoubleBl minY = Points[0].Y;

        DoubleBl maxX = Points[0].X;
        DoubleBl maxY = Points[0].Y;

        for (int i = 2; i < Points.Length - 1; i += 2) {
          // look at 0, 1, 2
          var q = PointBl.Bezier(.5, Points[i - 2], Points[i - 1], Points[i - 0]);

          minX = minX.Min(Points[i].X).Min(q.X);
          minY = minY.Min(Points[i].Y).Min(q.Y);
          maxX = maxX.Max(Points[i].X).Max(q.X);
          maxY = maxY.Max(Points[i].Y).Max(q.Y);
        }
        {
          var q = PointBl.Bezier(.5, Points[6], Points[7], Points[0]);
          minX = minX.Min(q.X);
          minY = minY.Min(q.Y);
          maxX = maxX.Max(q.X);
          maxY = maxY.Max(q.Y);
        }

        Func<PointBl, PointBl> Relative = (p) =>
          (p - new PointBl(minX, minY)) / new PointBl(maxX - minX, maxY - minY);
        ImageBrushBl brush = new ImageBrushBl() { ImageSource = Properties.Resources.Dock };
        shape.Effect.Custom = (input, uv) => {
          var right = PointBl.Bezier(uv.Y, 0, Relative(Points[2]), Relative(Points[3]), Relative(Points[4]));
          var left = PointBl.Bezier(1d - uv.Y, 0, Relative(Points[6]), Relative(Points[7]), Relative(Points[0]));

          var top = PointBl.Bezier(uv.X, 0, Relative(Points[0]), Relative(Points[1]), Relative(Points[2]));
          var bot = PointBl.Bezier(1 - uv.X, 0, Relative(Points[4]), Relative(Points[5]), Relative(Points[6]));

          var newX = (uv - left).Length / ((uv - right).Length + (uv - left).Length);
          var newY = (uv - top).Length / ((bot - uv).Length + (uv - top).Length);
          return ColorBl.FromScRGB(input[uv].ScA, brush[new PointBl(newX, newY)].ScRGB * input[uv].ScA);
        };
      }
      return () => { };

    }
  }

}
